
/* 
 * This file is part of FreeBMS - an open source battery management system
 *
 * Battery Regulator Module - PIC16F88
 * 
 * NOTE: This code is designed to be compiled using SOURCEBOOST (http://www.sourceboost.com)
 */
 
/* This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*
RA0 (AN0) = A/D Converter input for voltage sensor - Activated by RB1 set to low
RA1 (AN1) = unused
RA2 (AN2) = A/D - Thermistor Input - Activated by RA7 set to Low
RA3 (AN3) = unused
RA4 (AN4) = unused (no pullup!)
RA5 (CLR) = unused (not useable) 
RA6 (   ) = unused
RA7 (   ) = Enable for thermistor (low for enable, high for disable)

RB0 (INT) = Interrupt Line
RB1 (   ) = Enable for A/D Voltage sensor (low for enable, high for disable)
RB2 (RX ) = Serial RX
RB3 (PWM) = PWM output to MOSFET
RB4 (   ) = 
RB5 (TX ) = Serial TX
RB6 = Programming (unused)
RB7 = Programming (unused)
*/

/* EEPROM LAYOUT:
	0x00			0x01			0x02			0x03			0x04
	Chip ID			Error percent	Error percent	Error sign		LED configuration

	0x05			0x06			0x07			0x08
	Safety timer	Safety timer	Max voltage		Max voltage
*/

/* TODO:
 * Create some sort of DHCP method to dynamically assign chip IDs
 * Software based serial to lower chip cost
 * Ability to reprogram minimum and maximum cutoff voltages over serial line
 * Ability to reprogram pause and shunt timers over serial line
 */

// Global defines
#define SLEEP_TIMER		25	// Number of timer interrupts before going to sleep
#define MESG_HISTORY	5	// Number of messages to keep track of for voltage
#define WDT_ENABLED		1	// Whether or not watchdog timer is enabled

// LED Display Configuration Modes
#define LEDMODE_SHOW	0	// LED Mode 1:
							//  Red LED flashes based on oscillator, solid for shunting
							//	Blue LED flashes based on message receive
#define LEDMODE_SHOWMIN	1	// LED Mode 2:
							//  Red LED solid for shunting, does not flash
							//	Blue LED flashes based on message receive
#define LEDMODE_ERROR	2	// LED Mode 3:
							//  Red LED sets and locks upon encountering an error
							//  Blue LED flashes based on message receive
#define LEDMODE_SHUNT	3	// LED Mode 3:
							//  Red LED solid for shunting, does not flash
							//	Blue LED off
#define LEDMODE_OFF		4	// LED Mode 4:
							//  Red LED off
							//	Blue LED off

//#define LEAD_ACID		1	// We're using the Lead Acid system
#define LITHIUM_ION		1	// We're using the Lithium Ion System
 
// Global includes
#include <system.h>
#include "types.h"
#include "codes.h"
#include "serial.h"
 
// Set configuration bits
#pragma DATA _CONFIG, _CP_OFF & _PWRTE_OFF & _WDT_OFF & _INTRC_IO & _LVP_OFF & _BODEN_OFF

// Function prototypes
char eeprom_read(char address);
void eeprom_write(char address, char data);
unsigned short adc_measure(char channel);
void measure_voltage(BYTE num);

// ADC Bits
volatile bit adc_go @ ADCON0 . GO;
volatile bit adc_on @ ADCON0 . ADON;

// EEProm layout
#define EEPROM_CHIPID		0x00
#define EEPROM_ERR_HI		0x01
#define EEPROM_ERR_LO		0x02
#define EEPROM_ERR_SIGN		0x03
#define EEPROM_LEDMODE		0x04
#define EEPROM_STIMER_HI	0x05
#define EEPROM_STIMER_LO	0x06
#define EEPROM_MAXVOLT_HI	0x07
#define EEPROM_MAXVOLT_LO	0x08
#define EEPROM_REVISION		0x09
#define EEPROM_ADC_DELAY	0x0A

// Voltage initialization values
#define VOLTAGE_CLEAR		0xFF00		// Voltage state indicating value was cleared (missed ADC)
#define VOLTAGE_RESET		0xFF01		// Voltage state indicating chip has reset (WDT, BROWN-OUT)
#define VOLTAGE_SLEEP		0xFF02		// Voltage state indicating chip was in sleep
#define VOLTAGE_ADC_LO		0xFF03		// Voltage state indicating invalid low ADC value
#define VOLTAGE_ADC_HI		0xFF04		// Voltage state indicating invalid high ADC value
#define VOLTAGE_VOLT_LO		0xFF05		// Voltage state indicating invalid low voltage
#define VOLTAGE_VOLT_HI		0xFF06		// Voltage state indicating invalid high voltage

// Pinout mappings & conversion specific defines
/*#ifdef LEAD_ACID
#define ERROR_VOLT_MAX	1600  // Tag ADC value as invalid if we reach this voltage
#define ERROR_VOLT_MIN	800   // Tag ADC value as invalid if we reach this voltage
#define VOLT_CHANNEL	2	  // AD Channel for voltage reader
#define LED_BLUE		7	  // Bit for blue LED (porta)
#define LED_RED			0	  // Bit for red LED (porta)
#define SHUNTING_BIT	4	  // Bit for shunting pin (portb)
#else */
#define ERROR_VOLT_MAX	5000  // Tag ADC value as invalid if we reach this voltage
#define ERROR_VOLT_MIN	1500  // Tag ADC value as invalid if we reach this voltage
#define VOLT_CHANNEL	2	  // AD Channel for voltage reader
#define SHUNTING_BIT	3	  // Bit for shunting pin (portA)
#define LED_BLUE		1	  // Bit for blue LED (porta)
#define LED_RED			0	  // Bit for red LED (porta)
//#endif

//#define MAX_VOLTAGE	    14950 // Shunt if we go over this voltage
//#define MAX_VOLTAGE	    3625  // Shunt if we go over this voltage
//#define SHUNTING_TIMER	1800  // Number of timer interrupts for shunting safety timer

// Global defines
#define T1_PRELOAD_HI		0x3C  // Preloader for timer1 (hi byte): 100ms
#define T1_PRELOAD_LO		0xB7  // Preloader for timer1 (lo byte): 100ms

// Global variables
unsigned int sleep_counter;				  // Counter before going to sleep
unsigned int safety_counter;			  // Counter before turning off shunt
unsigned int safety_timer;				  // Safety timer for turning off shunt
unsigned int max_voltage;				  // Maximum voltage a cell can have before shunting
unsigned int batt_voltage[MESG_HISTORY+1];// Current voltage for the cell
bool shunting;							  // Whether or not the cell should be shunted
bool error_config;						  // Chip not properly configured
BYTE adc_delay;							  // Delay (ms) between stopping shunting and doing ADC
BYTE adc_id;							  // Current adc conversion ID
BYTE led_mode;							  // LED display configuration
BYTE locala;							  // Local copy of porta for read/write protection
BYTE localb;							  // Local copy of portb for read/write protection
BYTE localtrisa;						  // Local copy of trisa for protection
BYTE localtrisb;						  // Local copy of trisb for protection

// Variables for lead acid version
#ifdef LEAD_ACID
unsigned int error_percentage;			  // Error percentage for AD converter
unsigned long error_calc;				  // Auxillary variable for error percentage
bool error_sign;						  // Auxillary variable for error percentage
#endif

// Update whether or not LED 'num' is on
void set_LED(int num, bool state) {

	// If chip doesn't have a valid chip number, set LEDs to solid on
	if(chip_id == 0xFF) {
		state = true;
	}

	// Make sure LEDs aren't disabled in EEPROM
	if(led_mode != LEDMODE_OFF) {
		if(num == LED_BLUE) { // Blue LED
			locala.LED_BLUE = state;
		} else if(num == LED_RED) { // Red LED
			locala.LED_RED = state;
		}
		porta = locala;
	}
}

// Update whether or not LED 'num' is on
void toggle_LED(int num) {
	// If chip doesn't have a valid chip number don't toggle the LED
	if(chip_id == 0xFF) {
		return;
	}
	
	// Make sure LEDs aren't disabled in EEPROM
	if(led_mode != LEDMODE_OFF) {
		if(num == LED_BLUE) { // Blue LED
			toggle_bit(locala, LED_BLUE);
		} else if(num == LED_RED) { // Red LED
			toggle_bit(locala, LED_RED);
		}
		porta = locala;
	}
}

// Update whether or not we are shunting - INT
void set_shunting(bool val) {

	// If we have a configuration error, we don't shunt
	if(error_config == true) {
		val = false;
	}
	
#ifdef LEAD_ACID
	if(val == true) {
		set_bit(localb, SHUNTING_BIT);
		set_LED(LED_RED, true);
	} else {
		clear_bit(localb, SHUNTING_BIT);
		set_LED(LED_RED, false);
	}
	
	shunting = val;
	portb = localb;
#else
	if(val == true) {
		set_bit(locala, SHUNTING_BIT);
		set_LED(LED_RED, true);
	} else {
		clear_bit(locala, SHUNTING_BIT);
		set_LED(LED_RED, false);
	}
	
	shunting = val;
	porta = locala;
#endif
}

void main()
{
	// Configure oscillator for 2 MHz
	osccon = 01010000b;
	
#if WDT_ENABLED==1
	// Configure watchdog timer register (see pg 144 of datasheet)
	wdtcon = 00000101b;
#else
	#warning "Warning: Running regulator without watchdog timer enabled is dangerous"
#endif

	// Set ansel, trisa, trisb to initial values
	ansel = 00000100b;	// Channel 2 as input
	trisa = 00000100b;	// Channel 2 as input
	trisb = 10111101b;  // PortB configured for output

	// Initialize AD registers
	adcon0 = 10000001b;	// See pp 115-118 in datasheet: Was 11000001b
	adcon1 = 10000000b; // 			"  "
	
	// Enable LEDs
	clear_bit(trisa, LED_BLUE);
	clear_bit(trisa, LED_RED);

	// Initialize Shunting OFF
	shunting = false;

	// Enable USART functions on PORTB
	clear_bit(trisb, 5);	// Serial TX
	set_bit(trisb, 2);		// Serial RX

	// Enable shunting on PORTA or PORTB depending on the assignment
#ifdef LEAD_ACID
	// Start not shunting
	clear_bit(trisb, SHUNTING_BIT);
	clear_bit(portb, SHUNTING_BIT);
#else
	// Start not shunting
	clear_bit(trisa, SHUNTING_BIT);
	clear_bit(porta, SHUNTING_BIT);
#endif

	// Grab the local versions of porta and portb.
	// This is done to prevent a read-write conflict.
	locala = porta;
	localb = portb;

	// Grab the local versions of trisa and trisb.
	localtrisa = trisa;
	localtrisb = trisb;

	// Start with LEDs off
#ifdef LEAD_ACID
	localb.LED_BLUE = false;
	localb.LED_RED = false;
	portb = localb;
#else
	locala.LED_BLUE = false;
	locala.LED_RED = false;
	porta = locala;
#endif

	// Initialize the serial communications bus
	// Argument: Baud rate prescaler - See the UART section of datasheet
//	serial_init(51);	// 2400 Baud at 2MHz
	serial_init(12);	// 9600 Baud at 2MHz
//	serial_init(6);		// 19200 Baud at 2MHz

	// Initialize operating ID from EEPROM (addr 0x0)
	chip_id = eeprom_read(EEPROM_CHIPID);
	delay_10us(2);

#ifdef LEAD_ACID
	// Initialize error percentage from EEPROM
	error_percentage = (eeprom_read(EEPROM_ERRHI) << 8) | eeprom_read(EEPROM_ERRLO);
	delay_10us(2);
	error_sign = eeprom_read(EEPROM_ERRSIGN);
	delay_10us(2);
#endif
	
	// Read LED configuration mode from EEPROM
	led_mode = eeprom_read(EEPROM_LEDMODE);
	delay_10us(2);
	// Set default LED configuration
	if(led_mode == 0xFF) {
		led_mode = LEDMODE_OFF;
	}

	// Read shunting safety timer from EEPROM
	safety_timer = (eeprom_read(EEPROM_STIMER_HI) << 8) | eeprom_read(EEPROM_STIMER_LO);
	delay_10us(2);
	
	// Read maximum voltage from EEPROM
	max_voltage = (eeprom_read(EEPROM_MAXVOLT_HI) << 8) | eeprom_read(EEPROM_MAXVOLT_LO);
	delay_10us(2);

	// Read ADC delay value from EEPROM
	adc_delay = eeprom_read(EEPROM_ADC_DELAY);
	
	// If ADC delay isn't set, initialize to zero (no delay)
	if(adc_delay == 0xFF)
		adc_delay = 0x00;

	// Initialize AD Conversion History
	for(int i = 0; i < MESG_HISTORY+1; ++i) {
		batt_voltage[i] = VOLTAGE_RESET;
	}

	// Invalid chip ID, set LEDs to solid on.
	if(chip_id == 0xFF) {
	
#ifdef LEAD_ACID
		localb.LED_BLUE = true;
		localb.LED_RED = true;
		portb = localb;
#else
		locala.LED_BLUE = true;
		locala.LED_RED = true;
		porta = locala;
#endif
	}
	
	// Initialize pointers
	rxptr = 0;
	rxlength = 0;
	rxescape = 0;
	
	// Initialize the sleep counter to 0
	sleep_counter = 0;

	// Initialize interrupt registers to interrupt on receiving messages
	set_bit(pie1, RCIE);
	set_bit(intcon, GIE);
	set_bit(intcon, PEIE);

	// Invalid safety timer or maximum voltage
	// Set both LEDs and loop until we get a real value
	while(max_voltage == 0xFFFF
	|| safety_timer == 0xFFFF) {
		// Clear watchdog timer
		clear_wdt();

		// Configuration error
		error_config = true;
#ifdef LEAD_ACID
		toggle_bit(localb, LED_BLUE);
		toggle_bit(localb, LED_RED);
		portb = localb;
		delay_ms(50);
#else
		toggle_bit(locala, LED_BLUE);
		toggle_bit(locala, LED_RED);
		porta = locala;
		delay_ms(50);
#endif
	}

	// Initialize timer1
	set_bit(pie1, TMR1IE);
	tmr1h = T1_PRELOAD_HI; // Preload value into timer1 for 100ms delay, 1:1 prescaler
	tmr1l = T1_PRELOAD_LO;
	t1con = 00000001b;
	
	// If we got this far, clear error configuration
	error_config = false;

	// Loop indefinately
	while(1) {
		// Clear the watchdog timer
		clear_wdt();

#if WDT_ENABLED==1
		// Verify integrity of trisa and trisb
		// This can get randomly reset on the PIC16F88, bad things happen if they change
		if(trisa != localtrisa
		|| trisb != localtrisb) {
			// Clear interrupts
			clear_bit(pie1, RCIE);
			clear_bit(intcon, GIE);
			clear_bit(intcon, PEIE);
			clear_bit(pie1, TMR1IE);

			// Loop forever so the watchdog timer will reset us
			while(1) { }
		}
#endif
		// Set red LED to the value of shunting
#ifdef LEAD_ACID
		if(led_mode != LEDMODE_OFF && led_mode != LEDMODE_ERROR) {
			if(shunting == true
			&& test_bit(localb, LED_RED) == false) {
				set_bit(localb, LED_RED);
				portb = localb;
			} else {
				if(led_mode != LEDMODE_SHOW
				&& test_bit(localb, LED_RED) == true) {
					clear_bit(localb, LED_RED);
					portb = localb;
				}
			}
		}	
#else
		if(led_mode != LEDMODE_OFF && led_mode != LEDMODE_ERROR) {
			if(shunting == true
			&& test_bit(locala, LED_RED) == false) {
				set_bit(locala, LED_RED);
				porta = locala;
			} else {
				if(led_mode != LEDMODE_SHOW
				&& test_bit(locala, LED_RED) == true) {
					clear_bit(locala, LED_RED);
					porta = locala;
				}
			}
		}	
#endif
	}
}

// Main interrupt handler
void interrupt()
{
	// Activity on portB interrupt, wake from sleep
	if(test_bit(intcon, INTF))
		clear_bit(intcon, INTF);
	// If it was a timer interrupt
	if(test_bit(pir1, TMR1IF)) {

		// Preload value into timer1 for 100ms delay, 1:1 prescaler
		tmr1h = T1_PRELOAD_HI; 
		tmr1l = T1_PRELOAD_LO;
		
		// Clear interrupt flag
		clear_bit(pir1, TMR1IF);

		// Increment shunting counter if shunting
		if(shunting == true) {
			++safety_counter;
		}

		// Increment sleep counter
		sleep_counter++;

		// LED Debugging
		if(led_mode == LEDMODE_SHOW
		&& shunting == false)
			toggle_LED(LED_RED);

		// Turn shunt off if we're still shunting
		if(safety_counter > safety_timer) {
			set_shunting(false);
			safety_counter = 0;
		}
		
		// Go to sleep if no traffic
		if(sleep_counter > SLEEP_TIMER) {
			int i = 0;
			
			// Clear interrupts
			clear_bit(pie1, TMR1IE);
			clear_bit(t1con, TMR1ON);
			
			// Disable AD-converter
			adc_on = 0;

			// Stop shunting after interrupts are turned off
			set_shunting(false);

			// Clear out records
			for(i = 0; i < MESG_HISTORY; ++i) {
				batt_voltage[i] = VOLTAGE_SLEEP;
			}

			// Reset buffers
			rxptr = 0;
			rxlength = 0;
			rxescape = 0;
						
			// Turn off LEDs
			set_LED(LED_BLUE, false);
			set_LED(LED_RED, false);

			// Set wake-on-activity interrupt
			set_bit(intcon, INTE);

#if WDT_ENABLED==1
			// Turn off watchdog timer
			wdtcon = 00001000b;
#endif
			
			// Enter nano sleep
			asm
			{ 
				sleep 
			}
	
#if WDT_ENABLED==1
			// Turn on watchdog timer
			wdtcon = 00000101b;
#endif

			// Clear wake-on-activity interrupt
			clear_bit(intcon, INTE);

			// Re-enable AD-converter
			adc_on = 1;

			// Reset timer interrupts
			set_bit(pie1, TMR1IE);
			set_bit(t1con, TMR1ON);

			// Clear pointers and buffers
			rxptr = 0;

#ifdef LEAD_ACID
			// Initialize error percentage from EEPROM (addr 0x01 | 0x02)
			error_percentage = (eeprom_read(EEPROM_ERR_HI) << 8) | eeprom_read(EEPROM_ERR_LO);
			error_sign = eeprom_read(EEPROM_ERR_SIGN);
#endif
			
			// Read LED configuration mode from EEPROM
			led_mode = eeprom_read(EEPROM_LEDMODE);
			delay_10us(2);
			// Set default LED configuration
			if(led_mode == 0xFF) {
				led_mode = LEDMODE_OFF;
			}

			// Read safety timer from EEPROM
			safety_timer = (eeprom_read(EEPROM_STIMER_HI) << 8) | eeprom_read(EEPROM_STIMER_LO);
			delay_10us(2);
	
			// Read maximum voltage from EEPROM
			max_voltage = (eeprom_read(EEPROM_MAXVOLT_HI) << 8) | eeprom_read(EEPROM_MAXVOLT_LO);
			delay_10us(2);

			// Read ADC delay value from EEPROM
			adc_delay = eeprom_read(EEPROM_ADC_DELAY);
	
			// If ADC delay isn't set, initialize to zero (no delay)
			if(adc_delay == 0xFF)
				adc_delay = 0x00;

			// Reset sleep counter
			sleep_counter = 0;
			
			// Reset shunting counter
			safety_counter = 0;
		}
	} 

	// If type of interrupt is serial message received
	if(test_bit(pir1, RCIF)) 
	{
		// Receive and parse message
		if(receive_message()) {

			// Clear watchdog timer
			clear_wdt();
		
			// Zero the sleep counter everytime a message is received
			sleep_counter = 0;
			
			// (length) (destination) (type) (payload)* (checksum)
			if(rxbuffer[1] == chip_id 
			|| rxbuffer[2] == ID_START_VOLT
			|| rxbuffer[2] == ID_PROGRAM_ID
			|| rxbuffer[2] == ID_WAKEUP) {

				// LED Debugging
				if(led_mode == LEDMODE_SHOW
				|| led_mode == LEDMODE_SHOWMIN
				|| led_mode == LEDMODE_ERROR) {
					// Have each start conversion request set the LED on
					if(rxbuffer[2] == ID_START_VOLT 
					&& led_mode == LEDMODE_SHOW) {
						set_LED(LED_BLUE, true);
					} else {
						toggle_LED(LED_BLUE);
					}
				}

				// Parse request
				switch(rxbuffer[2]) {

					// Supervisor has requested chip wakeup. Flash the LEDs.
					case ID_WAKEUP:
						// LED Debugging
						if(led_mode == LEDMODE_SHOW) {
							set_LED(LED_BLUE, true);
							set_LED(LED_RED, true);
						}
						break;

					// Supervisor has requested start of voltage conversion
					case ID_START_VOLT:
						// LED Debugging
						if(led_mode == LEDMODE_SHOW) {
							set_LED(LED_BLUE, true);
						}

						if(rxbuffer[3] < MESG_HISTORY && rxbuffer[3] > 0) {
							// If ADC delay is programmed
							if(adc_delay > 0) {
								// If shunting, disable shunting before doing ADC
								if(shunting == true) {
									set_shunting(false);
									delay_ms(adc_delay);
									measure_voltage(rxbuffer[3]);
									set_shunting(true);
								} else {
									// Delay ADC, then poll the AD Converter
									delay_ms(adc_delay);
									measure_voltage(rxbuffer[3]);
								}
							} else {
								measure_voltage(rxbuffer[3]);
							}
						}
						break;

					// Supervisor has requested battery voltage
					case ID_VOLT:
						if(rxbuffer[3] < MESG_HISTORY && rxbuffer[3] > 0) {
						
							// If we encountered an error, set red LED
							if(led_mode == LEDMODE_ERROR && batt_voltage[rxbuffer[3]] >= 0xFF00) {
								set_LED(LED_RED, true);
							}
							
							// Load message buffer and send
							mesg_data[0] = (BYTE)((batt_voltage[rxbuffer[3]] >> 8) & 0x00FF);
							mesg_data[1] = (BYTE)(batt_voltage[rxbuffer[3]] & 0x00FF);
							mesg_data[2] = (BYTE)(shunting);
							send_message(ID_VOLT, chip_id, 3); // Send to supervisor chip

							// Zero out old records to avoid sending false data
							if(rxbuffer[3] == 1)
								batt_voltage[MESG_HISTORY - 1] = VOLTAGE_CLEAR;
							else
								batt_voltage[rxbuffer[3] - 1] = VOLTAGE_CLEAR;
						}
						break;
						
					// Supervisor has requested a change in shunting
					case ID_SHUNT:
						if(rxbuffer[3]) {
							set_shunting(true);
						} else {
							set_shunting(false);
						}

						// Reset shunting counter
						safety_counter = 0;

						// Send acknowledgement to supervisor chip
						mesg_data[0] = (BYTE) shunting;
						send_message(ID_SHUNT, chip_id, 1);
						break;
						
					// Supervisor has provided a new LED configuration mode
					case ID_PROGRAM_LEDS:
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Read from rxbuffer
							led_mode = rxbuffer[4];

							// Store in EEPROM
							eeprom_write(EEPROM_LEDMODE, led_mode);

							// Read from EEPROM
//							led_mode = eeprom_read(EEPROM_LEDMODE);

							// Clear LEDs
							set_LED(LED_BLUE, false);
							set_LED(LED_RED, false);
						}

						// Send acknowledgement to supervisor chip
						mesg_data[0] = (BYTE) led_mode;
						send_message(ID_PROGRAM_LEDS, chip_id, 1); 
#if WDT_ENABLED==1
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Disable interrupts
							clear_bit(intcon, GIE);

							// Let the watchdog timer reset us
							while(1) { }
						}
#endif
						break;

					// Supervisor has provided a new ADC shunting delay
					case ID_PROGRAM_DELAY:
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Read from message
							adc_delay = rxbuffer[4];
						
							// Store in EEPROM
							eeprom_write(EEPROM_ADC_DELAY, adc_delay);
							
							// Read from EEPROM
//							adc_delay = eeprom_read(EEPROM_ADC_DELAY);
						}
						
						// Send acknowledgement response to supervisor
						mesg_data[0] = (BYTE)(adc_delay);
						send_message(ID_PROGRAM_DELAY, chip_id, 1);
						
#if WDT_ENABLED==1
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Disable interrupts
							clear_bit(intcon, GIE);
						
							// Let the watchdog timer reset us
							while(1) { }
						}
#endif
						break;

/*					// Supervisor has provided a new chip ID
					case ID_PROGRAM_ID:
#warning "ID_PROGRAM_ID: Find two unused pins we can use"
						if(localb.8 == 1
						&& localb.7 == 1) {
							// Read from rxbuffer
							chip_id = rxbuffer[1];

							// Store in EEPROM
							eeprom_write(EEPROM_CHIPID, chip_id);

							// Re-read it to verify that it was correctly set
							chip_id = eeprom_read(EEPROM_CHIPID);

							// Send acknowledgement response to supervisor
							mesg_data[0] = chip_id;
							send_message(ID_PROGRAM_ID, chip_id, 1);
						}
						break;
*/
					// Supervisor has provided a new safety timer
					case ID_PROGRAM_TIMERS:

						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Read from message
							safety_timer = (rxbuffer[4] << 8) | rxbuffer[5];
						
							// Store in EEPROM
							eeprom_write(EEPROM_STIMER_HI, (safety_timer >> 8) & 0x00FF);
							eeprom_write(EEPROM_STIMER_LO, safety_timer & 0x00FF);
							
							// Read from EEPROM
//							safety_timer = (eeprom_read(EEPROM_STIMER_HI) << 8) | eeprom_read(EEPROM_STIMER_LO);
						}
						
						// Send acknowledgement response to supervisor
						mesg_data[0] = (BYTE)((safety_timer >> 8) & 0x00FF);
						mesg_data[1] = (BYTE)(safety_timer & 0x00FF);
						send_message(ID_PROGRAM_TIMERS, chip_id, 2);				
#if WDT_ENABLED==1
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Disable interrupts
							clear_bit(intcon, GIE);
						
							// Let the watchdog timer reset us
							while(1) { }
						}
#endif
						break;

					// Supervisor has provided a high voltage cut off
					case ID_PROGRAM_MAXVOLT:

						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Read from message
							max_voltage = (rxbuffer[4] << 8) | rxbuffer[5];

							// Store in EEPROM
							eeprom_write(EEPROM_MAXVOLT_HI, (max_voltage >> 8) & 0x00FF);
							eeprom_write(EEPROM_MAXVOLT_LO, max_voltage & 0x00FF);

							// Read from EEPROM
//							max_voltage = (eeprom_read(EEPROM_MAXVOLT_HI) << 8) | eeprom_read(EEPROM_MAXVOLT_LO);
						}
						
						// Send acknowledgement response to supervisor
						mesg_data[0] = (BYTE)((max_voltage >> 8) & 0x00FF);
						mesg_data[1] = (BYTE)(max_voltage & 0x00FF);
						send_message(ID_PROGRAM_MAXVOLT, chip_id, 2);	
#if WDT_ENABLED==1
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Disable interrupts
							clear_bit(intcon, GIE);
						
							// Let the watchdog timer reset us
							while(1) { }
						}
#endif
						break;

#ifdef LEAD_ACID
					// Supervisor has provided a new error correction percentage
					case ID_PROGRAM_ERROR:
					
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Calculate error percentage
							measure_voltage(0);
							
							// Read new value from receive buffer
							error_percentage = (rxbuffer[3] << 8) | rxbuffer[4];
							
							if(error_percentage > batt_voltage[0]) {
								error_calc = error_percentage * 100000;
								error_calc /= batt_voltage[0];
								error_sign = 1;
							} else {
								error_calc = batt_voltage[0] * 100000;
								error_calc /= error_percentage;
								error_sign = 0;
							}
	
							error_calc = error_calc - 100000;
							error_percentage = (unsigned int) error_calc;
	
							// Store in EEPROM
							eeprom_write(EEPROM_ERR_HI, (error_percentage >> 8) & 0x00FF);
							eeprom_write(EEPROM_ERR_LO, error_percentage & 0x00FF);
							eeprom_write(EEPROM_ERR_SIGN, error_sign);
	
							// Read from EEPROM
//							error_percentage = (eeprom_read(EEPROM_ERR_HI) << 8) | eeprom_read(EEPROM_ERR_LO);
						}
						
						// Send acknowledgement response to supervisor
						mesg_data[0] = (BYTE)((error_percentage >> 8) & 0x00FF);
						mesg_data[1] = (BYTE)(error_percentage & 0x00FF);
						mesg_data[2] = (BYTE)(error_sign);
						send_message(ID_PROGRAM_ERROR, chip_id, 3);
#if WDT_ENABLED==1
						// If rxbuffer[3] is not 1, we're only checking current value
						if(rxbuffer[3] == 1) {
							// Disable interrupts
							clear_bit(intcon, GIE);

							// Let the watchdog timer reset us
							while(1) { }
						}
#endif
						break;
#endif
						
					default:
						// Should not have gotten here
						break;
				}
			}
		}

		// Clear the interrupt bit
		clear_bit(pir1, RCIF);
	} 
}

void measure_voltage(BYTE num)
{
	unsigned long tmp_calc = 0;		// Auxillary variable for calculations
#ifdef LEAD_ACID
	unsigned long tmp_value = 0;	// Auxillary variable for calculations

	// Poll the AD Converter
	tmp_calc = adc_measure(VOLT_CHANNEL);

	// Make sure result is valid, set error code if it isn't valid
	if(tmp_calc > 1024) {
		tmp_calc = VOLTAGE_ADC_HI;
	} else if(tmp_calc <= 0) {
		tmp_calc = VOLTAGE_ADC_LO;
	} else {
		// Begin calculations
		tmp_calc *= 10000;
		tmp_calc /= 1024;
		tmp_calc *= 15;
	
		tmp_value = error_percentage * tmp_calc;
		tmp_value = tmp_value / 100000;
	
		if(num > 0 
		&& error_percentage != 0xFFFF 
		&& error_sign != 0xFF) {
			if(error_sign == 1)
				tmp_calc += tmp_value;
			else
				tmp_calc -= tmp_value;
		}
		tmp_calc /= 10;
	}
#else
	// Poll the AD Converter -- make sure we get a value greater than 0
	tmp_calc = adc_measure(VOLT_CHANNEL);

	// Make sure we got a valid result
	if(tmp_calc > 1024) {
		tmp_calc = VOLTAGE_ADC_HI;
	} else if(tmp_calc <= 0) {
		tmp_calc = VOLTAGE_ADC_LO;
	} else {
		// Actual voltage = Vref / ( x / 1024);
		tmp_calc *= 10000; // Multiply x by 10,000 so we can work with decimal numbers
		tmp_calc /= 1024; // Divide by 1024 to get % of A/D Converter range
		tmp_calc = 25000000 / tmp_calc;  //Vref == 2.5 volts x 10000 = 25,000
	}
#endif

	// If voltage is beyond the error tolerance, flag as error
	if(tmp_calc < 0xFF00) {
		if(tmp_calc > ERROR_VOLT_MAX) {
			tmp_calc = VOLTAGE_VOLT_HI;
		} else if(tmp_calc < ERROR_VOLT_MIN) {
			tmp_calc = VOLTAGE_VOLT_LO;
		}
	}
	
	// Emergency override check
	if(tmp_calc >= max_voltage
	&& tmp_calc <= 0xFF00) {
		// Set shunting pin
		set_shunting(true);

		// Reset shunting counter
		safety_counter = 0;
	}

	// If we have a configuration error, we produce error value
	if(error_config == true) {
		batt_voltage[num] = 0x00;
	} else {
		// Update table
		batt_voltage[num] = tmp_calc;
	}
}

char eeprom_read(char address)
{
	// Wait for write to complete
	while( eecon1.WR );

	// Load address
	eeadr = address;

	// Point to EEPROM memory
	eecon1.EEPGD = 0;
	
	// 18F devices also need the CFGS bit to be cleared
#ifdef _PIC18
	eecon1.CFGS = 0;
#endif

	// Read
	eecon1.RD = 1;
	
	// Return value
	return eedata;
}

void eeprom_write(char address, char data)
{
	// 18F devices also need the CFGS bit to be cleared
#ifdef _PIC18
	eecon1.CFGS = 0;
#endif

	// Wait for write to complete
	while( eecon1.WR );

	// Load address and data
	eeadr = address;
	eedata = data;

	// Point to EEPROM memory
	eecon1.EEPGD = 0;
	// Write enable
	set_bit(eecon1, WREN);
	// Disable interrupts
	clear_bit(intcon, GIE);

	// Required write sequence
	eecon2 = 0x55;
	eecon2 = 0xAA;
	set_bit(eecon1, WR);

	// Disable write
	clear_bit(eecon1, WREN);
	// Enable interrupts
	set_bit(intcon, GIE);

	// Wait for write to complete before continueing code execution
	while( eecon1.WR );
}

unsigned short adc_measure(char channel)
{
	// Clear the channel selection in adcon0
	adcon0 &= 11000111b;
	
	// Shift the channel value to the appropriate position
	channel = (channel & 0x07) << 3;
	
	// And move it to the appropriate position in adcon0
	adcon0 |= channel;

	// Wait the required acquisition time (datasheet)
	delay_10us(2);
	
	// Start the conversion
	adc_go = 1;
	
	// Wait until it is done
	while (adc_go);
	
	unsigned short retval;
	retval = (short)adresh;
	retval = retval << 8;
	retval |= adresl;

	return retval;
}